/*

 * Copyright (c) 2017-2018, FastDev 刘强 (fastdev@163.com).

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

package of.hotswapinject.plugin.imp;

import of.hotswapinject.core.common.Util;
import org.apache.commons.cli.Option;
import of.hotswapinject.core.plugin.IMethodEnterPlugin;
import of.hotswapinject.core.plugin.IMethodExitPlugin;
import of.hotswapinject.core.plugin.IMethodFilter;
import of.hotswapinject.core.plugin.MethodMeta;

import java.util.Stack;

public class MethodCallTrace extends MethodFilterBase {
    private static Option callTrace;
    private static ThreadLocal<Stack<String>> map = new ThreadLocal<Stack<String>>();

    static {
        callTrace = new Option("t", "callTrace", false, "trace method call");
        callTrace.setRequired(false);
    }

    @Override
    protected boolean canHandle(String key, MethodMeta adviceAdapter) {
        return true;
    }

    @Override
    protected boolean hookExitOnException(MethodMeta adviceAdapter) {
        return true;
    }

    private Stack<String> getStack(){
        Stack<String> stack = map.get();
        if(stack == null) {
            stack = new Stack<String>();
            map.set(stack);
        }
        return stack;
    }

    private String getLevelSkip() {
        Stack<String> stack = getStack();
        if(stack == null || stack.empty()) {
            return "";
        }
        else {
            int charCount =  (stack.size() - 1) * 4;
            if(charCount == 0) {
                return "";
            }
            StringBuilder sb = new StringBuilder(charCount);
            for(int i1 = 0; i1 < charCount; i1++) {
                sb.append(' ');
            }
            return sb.toString();

        }
    }

    public void inMethod(String key, String params) {
        getStack().push(key);
        Util.trace(getLevelSkip() + "call:" + key + ", param:" + params);
    }

    public void exitMethod(String params, String result) {
        Stack<String> stack = getStack();
        if(stack.empty()) {
            return;
        }
        String skip = getLevelSkip();
        String key = stack.pop();
        Util.trace(skip + "exit:" + key + ", param:" + params + ", result:" + result);
    }

    @Override
    public IMethodEnterPlugin.ReturnParam onEnter(String key, Object[] params) {
        inMethod(key, Util.toJSONString(params));
        return IMethodEnterPlugin.ReturnParam.none();
    }

    @Override
    public IMethodExitPlugin.ReturnParam onExit(String key, Object[] params, Object result) {
        exitMethod(Util.toJSONString(params), Util.toJSONString(result));
        return IMethodExitPlugin.ReturnParam.none();
    }

    @Override
    public Option getOption() {
        return callTrace;
    }
}